package ws.very.util.orient

import com.tinkerpop.blueprints.impls.orient._
import com.tinkerpop.{ blueprints => b }
import scala.util.Try
import ws.very.util.stuct.DyMap
import scala.language.dynamics
import com.tinkerpop.blueprints.impls.orient.OrientEdgeType
import java.{ util => ju }
import java.{ math => jm }
import scala.collection.convert._
import scala.collection.convert.WrapAsScala
import ws.very.util.lang._
import ws.very.util.stuct._

object SysKeyword extends Enumeration {
  val ###, rid = Value
}

case class Function(name: S, code: S, params: Seq[S] = Nil, typ: scriptType.Value = scriptType.javascript)

object Index {
  object Type extends Enumeration {
    val unique, notUnique, fullText, dictionary, unique_hash_index, notunique_hash_index, fulltext_hash_index, dictionary_hash_index = Value
  }
  object Collate extends Enumeration {
    val ci = Value

  }

  type KeyType = com.orientechnologies.orient.core.metadata.schema.OType

  object KeyType {
    import com.orientechnologies.orient.core.metadata.schema.OType._
    val str = STRING
    val bool = BOOLEAN
    val int = INTEGER
    val long = LONG
    val double = DOUBLE
    val dateTime = DATETIME

  }
  //  def keyType = com.orientechnologies.orient.core.metadata.schema.OType

}
trait SQL
object SQL {

  object updateOp extends Enumeration {
    val SET, INCREMENT, ADD, REMOVE, PUT = Value
    def sql(value: Value) = value.toString()
  }
  object returnType extends Enumeration {
    val COUNT, BEFORE, AFTER = Value
    def sql(value: Value) = s"return $value.toString()"
  }
  object functions extends Enumeration {
    object Misc {
      val `sysdate()` = Value
    }
    private val names = values.map { _.toString }

    def contains(name: S) = names.contains(name)
  }
  case class updateKVs(kvs: Map[S, A]) {
    def this(kvs: (S, A)*) = this(kvs.toMap)
    def withSysDate(field: S*) = this.copy(kvs ++ (field.map { k => k -> functions.Misc.`sysdate()`.toString() }.toMap))
    def withModifyTime = withSysDate("modifyTime")
    def preNameSql = kvs.map {
      case (k, v) =>
        if (functions.contains(v.toString()) || v.toString().startsWith(":")) s"$k=$v"
        else s"$k=:$k"
    }.mkString(",")
  }
  implicit def map2kvs(kvs: Map[S, A]) = updateKVs(kvs)

  //http://orientdb.com/docs/1.7.8/orientdb.wiki/SQL-Where.html
  case class where(sql: S)

  //  object update {
  //    def apply(clazz: S /*FIXME*/ , op: (updateOp.Value), opVal: updateKVs, whereCond: where, upsert: B = false, returnTyp: returnType.Value = returnType.COUNT /*,limit:O[I]=None*/ ): update =
  //      update(clazz, Map(op -> opVal), whereCond, upsert, returnTyp)
  //  }
  //  http://orientdb.com/docs/1.7.8/orientdb.wiki/SQL-Update.html
  case class update(clazz: S /*FIXME*/ , ops: Map[updateOp.Value, updateKVs], whereCond: where, upsert: B = false, returnTyp: returnType.Value = returnType.COUNT /*,limit:O[I]=None*/ ) extends SQL {
    def this(clazz: S /*FIXME*/ , op: (updateOp.Value), opVal: updateKVs, whereCond: where /*, upsert: B = false, returnTyp: returnType.Value = returnType.COUNT ,limit:O[I]=None*/ ) =
      this(clazz, Map(op -> opVal), whereCond)
    def preNameSql = s"""update $clazz ${
      ops.map {
        case (op, opVal) =>
          s"${updateOp.sql(op)} ${opVal.preNameSql}"
      }.mkString(" ")
    } ${if (upsert) "upsert" else ""}  ${returnType.sql(returnTyp)} where ${whereCond.sql}"""
  }
}
case class IndexParameters(typV: Index.Type.Value = Index.Type.notUnique,
    keyTypeV: Index.KeyType = Index.KeyType.str,
    classNameV: S = "" //,collateV: Index.Collate.Value = null
    ) {

  def typ(v: Index.Type.Value = Index.Type.unique) = copy(typV = v)
  def className(name: S) = copy(classNameV = name)
  //  def collate(v: Index.Collate.Value = Index.Collate.ci) = {
  //    collateV = v
  //    this
  //  }

  def keyType(v: Index.KeyType) = copy(keyTypeV = v)

  def toIndexParameters = {
    val map = Map("type" -> typV, "class" -> classNameV,
      "keytype" -> keyTypeV.name //, "collate" -> collateV
      ).collect {
        case (k, v) if v != null && v.toString.nonEmpty =>
          k -> v
      }
    map.map {
      case (k, v) =>
        new b.Parameter(k, v)
    }.toSeq
  }

}

object Direction extends Enumeration {
  val out, in, both = Value
  def old(value: Value) =
    value match {
      case this.out => b.Direction.OUT
      case this.in => b.Direction.IN
      case this.both => b.Direction.BOTH
    }
}
trait BaseGraph { my =>
  import wrapAsJava._
  import decorateAsJava._
  def graphConf: GraphConf
  protected def self: OrientBaseGraph
  protected[orient] def isClosed = self.isClosed()
  protected[orient] def shutdown =
    try {
      self.shutdown()
    } catch {
      case e => e.printStackTrace()
    }

  protected def convertN2O(v: A): Object = {
    v match {
      case b: BigDecimal => b.bigDecimal
      case m: Map[A, A] => mapAsJavaMap(m.map { case (k, v) => convertN2O(k) -> convertN2O(v) })
      case i: Itb[A] =>
        new java.util.LinkedList[A](((i.map { t => convertN2O(t) }).asJavaCollection))
      case other => other.asInstanceOf[Object]
    }
  }
  protected def convertO2N(v: A) = v match {
    case b: jm.BigDecimal => BigDecimal(b)
    case v: b.Vertex => convert(v)
    case e: b.Edge => convert(e)
    case other => other
  }
  protected def propsConvert(props: S Map Any): ju.Map[S, A] =
    props.flatMap {
      case (k, v) =>
        (k, convertN2O(v))
    }

  object Tag {
    val empty = Tag(graphConf.defaultClass.orNull, null)
  }
  case class Tag(clazz: S = graphConf.defaultClass.orNull, cluster: S = null) {
    lazy val str = Map("class" -> clazz, "cluster" -> cluster).collect {
      case (k, v) if v =>
        s"$k:$v"
    }.mkString(",")
    override def toString = str
  }
  trait Ele[T <: b.Element] extends DyMap {
    protected[orient] def old: T

    def keySet = old.getPropertyKeys.toSet
    def selectDynamic(field: S): A =
      field match {
        case t if t == SysKeyword.###.toString() || t == SysKeyword.rid.toString() => old.getId
        case _ => convertO2N(old.getProperty(field))
      }
    def updateDynamic(field: S)(value: A): this.type = {
      old.setProperty(field, convertN2O(value))
      this
    }
    private def check(field: S) = if (SysKeyword.values.exists { x => x.toString == field }) throw new RuntimeException(s"别用关键字 $field 做参数")
    def applyDynamic(field: S)(value: A): this.type = {
      check(field)
      updateDynamic(field)(value)

    }
    def applyDynamicNamed(method: S)(kvs: (S, A)*): this.type = {
      method match {
        case "props" => kvs.filter { case (field, _) => field }.foreach { case (k, v) => updateDynamic(k)(v) }
      }
      this
    }
    def select(fields: S*) = {
      DyMapImpl(fields.map { f => f -> selectDynamic(f) }.toMap)
    }
    def remove = old.remove()

    def rid = selectDynamic(SysKeyword.###.toString()).toString()

    def unset(field: S) = {
      check(field)
      old.removeProperty(field)
    }

    def ele = new MyGet2[Ele[_]] {
      def any2MyType(any: A) = OrientType(any).asEle
      //        any match {
      //        case e: Ele[_] => e
      //      }
    }
    def eleItb = new MyGet2[Itb[Ele[_]]] {
      def any2MyType(any: A) = OrientType(any).asEleItb
    }

    def orient = new MyGet2[OrientType] {
      def any2MyType(any: A) = OrientType(any)
    }

    def value = selectDynamic("value")

    def props = old.getPropertyKeys.map { x => x -> selectDynamic(x) }.toMap

    def toMap = props + (SysKeyword.rid.toString() -> rid)

    override def equals(other: Any) = if (other.isInstanceOf[Ele[_]]) old == other.asInstanceOf[Ele[_]].old else false

    override def toString = toMap.toString()
  }
  trait Vertex extends Ele[b.Vertex] {
    def edges(dir: Direction.Value, labels: S*) =
      old.getEdges(Direction.old(dir), labels: _*).map { convert }

    def vertices(dir: Direction.Value, labels: S*) = old.getVertices(Direction.old(dir), labels: _*).map { convert }

    def -->(to: BaseGraph#Vertex, label: S = null, tag: BaseGraph#Tag = Tag.empty) =
      my.add.edge(this, to, label, tag)
    //createEdgeProperty(Direction.OUT, "Photos").setOrdered(true)
  }

  protected def convert(o: b.Vertex): Vertex = new Vertex {
    val old = o

  }
  protected def convertE[T <: b.Element, R <: Ele[T]](o: T) = o match {
    case t: b.Vertex => convert(t)
    case t: b.Edge => convert(t)
    case null => null
  }

  trait Edge extends Ele[b.Edge] {
    def inVertex = old.getVertex(b.Direction.IN)
    def outVertex = old.getVertex(b.Direction.OUT)
    def label = old.getLabel
    //as OrientEdge?
    //    def edges(dir: Direction.Value, labels: S*) =
    //      old.getEdges(Direction.old(dir), labels:_*).map{convert}
  }

  protected def convert(o: b.Edge): Edge = new Edge {
    val old = o
  }

  protected class addOp {
    def vertex(props: S Map Any = Map(), tag: Tag = Tag.empty) =
      convert(self.addVertex(tag.str, propsConvert(props)))
    def edge(from: BaseGraph#Vertex, to: BaseGraph#Vertex, label: S = null, tag: BaseGraph#Tag = Tag.empty) =
      convert(self.addEdge(tag.str, from.old, to.old, label))
  }
  val add = new addOp
  val + = add

  protected class removeOp {
    def apply(e: BaseGraph#Edge) = self.removeEdge(e.old)
    def apply(v: BaseGraph#Vertex) = self.removeVertex(v.old)
  }
  val remove = new removeOp
  val - = remove

  protected trait Base[T] extends Dynamic {
    private def keyConvert(key: S) = if (key.contains(".")) key else graphConf.defaultClass.map { clazz => s"$clazz.$key" }.getOrElse(key)

    protected def eqQuery(key: S, eq: A): Itb[T]

    def updateDynamic(key: S)(eq: A) = applyDynamic(key)(eq)

    def applyDynamic(key: S)(eq: A) = eqQuery(keyConvert(key),
      convertN2O(eq))

    def +(props: S Map Any = Map(), tag: Tag = Tag.empty) = my.+.vertex(props, tag)

    @deprecated
    def findOrInit(key: S, eq: A)(init: (my.addOp) => T): T = {
      val v = applyDynamic(key)(eq)
      v.headOption.getOrElse {
        init(my add)
      }
    }

  }
  object Vertices extends Base[Vertex] {
    def apply(clazz: S = graphConf.defaultClass.getOrElse(OrientVertexType.CLASS_NAME), polymorphic: B = true) =
      self.getVerticesOfClass(clazz, polymorphic).map { v => convert(v) }

    def eqQuery(key: S, eq: A) = self.getVertices(key, eq).map { convert }

    object Indices {
      def keyIndex(key: S) = my.Indices.verticesKeyIndex(key)
    }

    def createClass(name: S) = Classes + (name, "V")
    // FIXME:getVertices
  }

  object Classes {
    //FIXME:多继承
    def +(name: S, superClass: O[S] = None, isAbs: B = false) =
      sql(s"CREATE CLASS $name ${superClass.map { x => s"extends $x" }.getOrElse("")} ${if (isAbs) "ABSTRACT" else ""}").execute
  }

  object Edges extends Base[Edge] {
    def apply(clazz: S = graphConf.defaultClass.getOrElse(OrientEdgeType.CLASS_NAME), polymorphic: B = true) =
      self.getEdgesOfClass(clazz, polymorphic).map { e => convert(e) }
    def eqQuery(key: S, eq: A) = self.getEdges(key, eq).map { convert }
    def createClass(name: S) = Classes + (name, "E")
  }

  object Indices {
    def verticesKeyIndex(key: S) = keyIndex(key, classOf[b.Vertex])
    def keyIndex(key: S, clss: Class[_ <: b.Element]) = new {
      def create(ps: IndexParameters = IndexParameters()) =
        self.createKeyIndex(key, clss, {
          if (!ps.classNameV)
            graphConf.defaultClass.map { x => ps.className(x) }.getOrElse(ps)
          else ps
        }.toIndexParameters: _*)

      def drop = self.dropKeyIndex(key, clss)
    }
  }

  def apply(sqlOp: SQL, args: Map[S, A]): sql = (sqlOp) match {
    case (update: SQL.update) => sql(update.preNameSql, args)
  }

  case class OrientType(value: A) {
    def as[R] = value.asInstanceOf[R]
    def asEleItb = {
      val itb = (as[OrientDynaElementIterable]: Itb[A])

      itb.map { r => convertE(r.asInstanceOf[b.Element]) }
    }
    def asEle = convertE(as[b.Element])
  }
  protected abstract class BaseCmdOp(cmd: S, args: Seq[A]) {
    //println(sql)
    //     val argsO = args.map { a => a.asInstanceOf[Object] }
    //FIXME:服务端不持有 java.lang.ClassNotFoundException
    import com.orientechnologies.orient.core.command._
    object query {
      import com.orientechnologies.orient.core.sql.query._
      //      def sync[T] = self.command(new OSQLSynchQuery[T](sql)).execute(argsO: _*)
      //      def async[T] = self.command(new OSQLAsynchQuery[T](sql)).execute(argsO: _*)

      //      def nonBlocking[T]=self.command(new OSQLNonBlockingQuery[T](sql)).execute(args: _*)
    }
    protected def wrapReq: OCommandRequest
    private def call = OrientType(self.command(wrapReq).execute[A]((args).map { convertN2O }.toSeq: _*))
    def execute = OrientType(apply[A])
    def to[R] = apply[R]
    def countInfluence = to[I]
    private var logLen: O[I] = graphConf.defaultLogSetting
    def setLog(len: O[I]) = {
      logLen = len
      this
    }
    def disableLog = setLog(None)

    private def apply[R]() = logLen.map { l =>
      StopWatch {
        call.as[R]
      } { t => println(s"${if (l < 1) toString else toString.take(l)}.. takes $t") }
    }.getOrElse(call.as[R])
    /*
     * CREATE VERTEX TestClass set name='n5' 
     * //FIXME:com.tinkerpop.blueprints.impls.orient.OrientVertex cannot be cast to java.lang.Iterable
     */
    def exec = apply[OrientDynaElementIterable]: Itb[A]

    def eleResult = execute.asEleItb
    def vResult = exec.map { r => convert(r.asInstanceOf[b.Vertex]) }
    def asDyMapTable =
      DyMapTable(eleResult.map { _.toMap })
    def asDyTableImpl = DyTableImpl.from(asDyMapTable)
    def firstVResult = vResult.headOption
    def firstEleResult = eleResult.headOption
  }

  case class sql(sql: S, args: A*) extends BaseCmdOp(sql, args) {
    import com.orientechnologies.orient.core.sql._
    protected def wrapReq = new OCommandSQL(sql)
  }
  protected class script(typ: scriptType.Value, scriptText: S, args: Seq[A]) extends BaseCmdOp(scriptText, args) {
    import com.orientechnologies.orient.core.command.script._
    protected def wrapReq = new OCommandScript(typ.toString(), scriptText)
  }

  case class sqlScript(sql: S, args: A*) extends script(scriptType.sql, sql, args)

  case class jsScript(run: S, args: A*) extends script(scriptType.js, run, args)

  object functions {
    def del(name: S) = sql(s"delete from  OFunction where name='$name'").execute
    @deprecated
    def delAndCreate(name: S, code: S, paramNames: Seq[S] = Nil, typ: scriptType.Value = scriptType.js) = {
      del(name)
      sql(s"""CREATE FUNCTION $name "${code.replaceAll(""""""", "'")}"
                ${if (paramNames.nonEmpty) s"PARAMETERS [${paramNames.mkString(",")}]" else ""}
                LANGUAGE ${typ.toString()}
                
""").execute
    }

    def apply(func: Function) = new {
      def upsert = sql(s"""update OFunction set name=?,code=?,	language=? ,parameters=? upsert where name=? """, func.name, func.code, func.typ.toString(), func.params, func.name).execute
    }
    //    def load = {
    //      self.getRawGraph().getMetadata().getFunctionLibrary().load()
    //      this
    //    }

    def apply(name: S) = new {
      def delete = del(name)
      // private lazy val fun = self.getRawGraph().getMetadata().getFunctionLibrary().getFunction(name)
      def apply(args: A*) = {
        val argsIndex = args.zipWithIndex.map { case (v, i) => ("p" + i) -> v }
        val txt = s"$name(${argsIndex.map { _._1 }.mkString(",")})"
        println(txt)
        jsScript(txt, argsIndex.toMap)
      } // fun.execute(args.map { convertN2O }.toSeq: _*) //convertN2O
      //      def call(args: Map[S, A]) =jsScript(s"$name(${args})") fun.execute(args.mapValues { convertN2O })
    }

  }

}

object scriptType extends Enumeration {
  val sql, javascript = Value
  def js = javascript
}

trait Graph extends BaseGraph {

  def commit = self.commit()

  def rollback = self.rollback()
}

trait GraphNoTx extends BaseGraph {

}

trait GraphConf {

  def defaultClass: O[S]
  def defaultLogSetting:O[I]
}

case class GraphConf1(defaultClass: O[S] = None,defaultLogSetting:O[I]=Some(500)) extends GraphConf
case class GraphConfWithoutLog(defaultClass: O[S] = None,defaultLogSetting:O[I]=None) extends GraphConf
trait GraphFac {
  protected def self: OrientGraphFactory

  private val my = this

  lazy val baseUse = use()
  
  def apply() = baseUse

  def use(conf: GraphConf = GraphConf1()) = new {

    protected def gtx: Graph = new Graph {
      val self = my.self.getTx
      val graphConf = conf
    }

    protected def gnoTx: GraphNoTx = new GraphNoTx {
      val self = my.self.getNoTx
      val graphConf = conf
    }
    private def base[R, T <: BaseGraph](get: => T) = {
      (call: T => R) =>
        val g = get
        val r = Try {
          call(g)
        }
        if (r.isFailure)
          r.failed.foreach { _.printStackTrace() }
        g.shutdown

        r.get
    }
    /**
     * FIXME: val cur=g.sql(s"select ifnull(value2.intValue10,0) as value  from kvset where key ='$key'").firstEleResult
     * val value= cur.map{_.bigDec.value}.getOrElse(BigDecimal(0))+1
     * g.sql(s"""update kvset put value2='intValue10',? set key='$key' upsert return after $$current.value2.intValue10 where key='$key'""",value).firstEleResult
     * 因分段错误
     */
    def tx[R] = base[R, Graph](gtx)

    def autoTx[R] = (tryAutoTx[R] _).andThen { _.get }
    @scala.annotation.tailrec
    def autoRetryTx[R](call: Graph => R): R = {
      val r = tryAutoTx(call)
      if (r.isFailure) {
        r.failed.foreach { x => x.printStackTrace() }
        autoRetryTx(call)
      } else r.get
    }
    def tryAutoTx[R](call: Graph => R) = tx[Try[R]] { g =>
      val r = Try {
        val r = call(g)
        g.commit
        r
      }
      if (r.isFailure)
        try {
          r.failed.foreach { _.printStackTrace() }
          g.rollback
        } catch { case e => e.printStackTrace() }

      r
    }
    def noTx[R] = base[R, GraphNoTx](gnoTx)
    def noTxTry[R](call: GraphNoTx => R) = noTx[Try[R]] { g => Try { call(g) } }
  }
}
object GraphFac {
  trait withFunction extends GraphFac {
    baseUse.noTxTry {
      _.functions.delAndCreate("queryOrExec", """
var gdb = null;
if(dbType =="Graph")
  gdb=orient.getGraph();
else 
  gdb=orient.getGraphNoTx();

if(typeof(queryParams) != 'object')
  queryParams=[queryParams];

if(typeof(execParams) != 'object')
  execParams=[execParams];


var result = gdb.command("sql",querySql,queryParams);
if(result ==null || result.length==0)
  result= gdb.command("sql",execSql,execParams);

return result;
""", """"querySql","queryParams","execSql","execParams","dbType"""".delAll(""""""").split(","))
    }.failed.foreach { _.printStackTrace() }
  }
}

object Graph {
  object Mode extends Enumeration {
    val plocal, memory, remote = Value
  }
  def use(fac: OrientGraphFactory) = new GraphFac with GraphFac.withFunction { def self = fac }

  def remote(ipAndPort: S, db: S, user: S = OrientBaseGraph.ADMIN, pwd: S = OrientBaseGraph.ADMIN) = new {
    def fac(iMin: I = 1, iMax: I = 10) = use(new OrientGraphFactory(s"remote:$ipAndPort/$db").setupPool(iMin, iMax))
  }

}